package br.edu.ufcg.unknown.geradorDeCodigo;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Stack;
import java.util.StringTokenizer;

/**
 * Implementação do Gerador de Código Intermediário.
 * 
 * @author Fábio Jorge
 * @author Jaindson Valentim
 * @author Moisés Rodrigues
 */
public class GeradorDeCodigo {

	/**
	 * Linha de código atual do gerador de código
	 */
	private int triplaAtual;

	/**
	 * Instância única do GeradorDeCódigo
	 */
	private static GeradorDeCodigo instance = null;

	/**
	 * Pilha de Labels
	 */
	private Stack<String> pcl;

	/**
	 * Pilha de Operadores
	 */
	private Stack<String> pco;
	
	/**
	 * Pilha com os lexemas passados pelo Analisador Sintático
	 */
	private Stack<String> pcLexema;

	/**
	 * Contador de variáveis temporárias
	 */
	private int contTemp;

	/**
	 * Contador de labels
	 */
	private int contLabel;

	/**
	 * Tabela de Labels. Possui como chave o nome do Label e como valor a linha
	 * do código gerado pra onde o Label aponta.
	 */
	private Map<String, Integer> tabelaDeLabels;

	/**
	 * Armazena os códigos gerados até então pelo Gerador de Código
	 */
	private List<String> codigos;
	
	/**
	 * Armazena o lado esquerdo da atribuição
	 */
	private String ladoEsquerdo;

	/**
	 * Cria um novo gerador de codigo inicializando as pilhas de controle de labels, operandos e lexemas
	 * reseta o contador da tripla atual, temporarios e labels e  reinicializa a tabela de labels e a 
	 * lista de codigos gerados
	 */
	public GeradorDeCodigo() {
		this.pcl = new Stack<String>();
		this.pco = new Stack<String>();
		this.pcLexema = new Stack<String>();
		this.triplaAtual = 1;
		this.tabelaDeLabels = new HashMap<String, Integer>();
		this.codigos = new LinkedList<String>();
		resetTemp();
		resetLabel();
	}

	/**
	 * (Re)Inicializa a variável temporária
	 */
	public void resetLabel() {
		this.contLabel = 1;
	}

	/**
	 * (Re)Inicializa a variável temporária
	 */
	public void resetTemp() {
		this.contTemp = 1;
	}

	/**
	 * @return O Operador do topo da pilha de operadores
	 */
	public String popPcO() {
		if (this.pco.isEmpty()) {
			return null;
		}
		return this.pco.pop();
	}

	/**
	 * @return O Label do topo da pilha de labels
	 */
	public String popPcL() {
		if (this.pcl.isEmpty()) {
			return null;
		}
		return this.pcl.pop();
	}

	/**
	 * Insere um label no topo da pilha de labels
	 * 
	 * @param simbolo
	 *            Label a ser adicionado no topo da pilha de labels
	 */
	public void pushPcL(String simbolo) {
		this.pcl.push(simbolo);
	}

	/**
	 * @return Um novo Label
	 */
	public String makeLabel() {
		return "L" + this.contLabel++;
	}

	/**
	 * 
	 * @param label
	 */
	public void insere(String label) {
		this.tabelaDeLabels.put(label, this.triplaAtual);
	}

	/**
	 * <AG-1> [Re]inicializa o contador de temporárias
	 * reset_temp();
	 */
	public void AG1_cond() {
		resetTemp();
	}

	/**
	 * Guardar o lado esquerdo de uma atribuição e iniciar o
	 * contador de temporárias em 1 
	 * lado_esquerdo = símbolo;
	 * reset_temp();
	 */
	public void AG1_exp() {
		this.ladoEsquerdo = this.pcLexema.pop();
		resetTemp();
	}

	/**
	 * Gerar código para atribuição
	 * oper_1 = pop (PcO);
	 * emitir (lado_esquerdo, ‘=’, oper_1);
	 */
	public void AG2_exp() {
		String oper1 = popPcO();
		emitir(this.ladoEsquerdo, ":=", oper1);
	}

	/**
	 * Gera código para teste da condição com respectivo desvio
	 * emitir( "", oper_relacional, lado_esquerdo, pop( PcO ) );
	 * label = mklabel();
	 * push( PcL, label );
	 * emitir( label, "jif", "", "" );
	 */
	public void AG4_cond() {
		String label = makeLabel();
		pushPcL(label);
		emitir(label, "jif");
	}

	/**
	 * Resolve endereço do label anterior e gera desvio da parte else
	 * label-aux = pop( PcL );
	 * label = mklabel();
	 * push PcL, label );
	 * emitir( label, "jmp", "", "" );
	 * insere( Tab_label, label_aux, endereço_tripla_atual );
	 */
	public void AG5_cond() {
		String labelAux = popPcL();
		String label = makeLabel();
		pushPcL(label);
		emitir(label, "jmp");
		insere(labelAux);
	}

	/**
	 * Resolve endereço do label de fim de if
	 * label = pop( PcL );
	 * insere( Tab_label, label, endereço_tripla_atual );
	 */
	public void AG6_cond() {
		String label = popPcL();
		insere(label);
	}

	/**
	 * Gera label de início do ciclo
	 * label = mklabel();
	 * insere( Tab_label, label, endereço_tripla_atual );
	 * push( PcL, label );
	 */
	public void AG7_while() {
		String label = makeLabel();
		insere(label);
		pushPcL(label);
	}

	/**
	 * Gera desvio para início do ciclo e resolve endereço para label de fim de while
	 * 
	 * label-fim = pop( PcL );
	 * label-início = pop( PcL );
	 * emitir( label-início, "jmp", "", "" );
	 * insere( Tab_label, label-fim, endereço_tripla_atual );
	 */
	public void AG8_while() {
		String labelFim = popPcL();
		String labelInicio = popPcL();
		emitir(labelInicio, "jmp");
		insere(labelFim);
	}

	/**
	 * Adiciona o codigo gerado a lista de codigos de acordo com os
	 * parametros recebidos
	 */
	public void emitir(String op1, String op2, String op3, String op4) {
		this.codigos.add(this.triplaAtual++ + ". " + op1 + " " + op2 + " " + op3 + " " + op4);
	}

	/**
	 * Adiciona o codigo gerado a lista de codigos de acordo com os
	 * parametros recebidos
	 */
	public void emitir(String op1, String op2, String op3) {
		this.codigos.add(this.triplaAtual++ + ". " + op1 + " " + op2 + " " + op3);
	}

	/**
	 * Adiciona o codigo gerado a lista de codigos de acordo com os
	 * parametros recebidos
	 */
	public void emitir(String op1, String op2) {
		this.codigos.add(this.triplaAtual++ + ". " + op1 + " " + op2);
	}

	/**
	 * @return Uma variável temporária
	 */
	public String makeTemp() {
		return "t" + this.contTemp++;
	}

	/**
	 * Insere um Operador no topo da pilha de operadores
	 * @param simbolo
	 */
	public void pushPcO(String simbolo) {
		this.pco.push(simbolo);
	}

	public static GeradorDeCodigo getInstance() {
		if (instance == null) {
			instance = new GeradorDeCodigo();
		}
		return instance;
	}
	
	/**
	 * Insere o lexema do token atual
	 * 
	 * @param lexema
	 */
	public void pushLexema(String lexema) {
		this.pcLexema.push(lexema);
	}

	/**
	 * @return Todos os códigos gerados pelo Gerador de Código Intermediário
	 */
	public List<String> getCodigos() {
		return this.codigos;
	}

	/**
	 * (Re)Inicializa a lista de codigos gerados
	 */
	public void resetCodigos() {
		this.codigos.clear();
		
	}

	/**
	 * (Re)Inicializa o contador de triplas
	 */
	public void resetTriplaAtual() {
		this.triplaAtual = 1;
	}

	/**
	 * Atualiza o código gerado, modificando os labels gravados na tabela de labels
	 *
	 */
	public void atualizaCodigo() {
		
		List<String> novosCodigos = new LinkedList<String>();
		StringTokenizer st = null;
		String token = null;
		
		for(String codigo : this.codigos){
			
			String linha = codigo;
			st = new StringTokenizer(linha);
			
			if(st.countTokens() == 3 && (st.nextToken() != null && st.nextToken() != null && ((token = st.nextToken()).equals("jmp") || token.equals("jif")))){
				
				String nLinha = "";
				String label = "";
				String comando = "";
				Scanner sc = new Scanner(linha);
				if(sc.hasNext()) nLinha = sc.next();
				if(sc.hasNext()){
					String l = sc.next();
					if(this.tabelaDeLabels.containsKey(l)){
						label = "#" + this.tabelaDeLabels.get(l);
					}else{
						label = l;
					}
				}
				if(sc.hasNext()) comando = sc.next();
				
				codigo = nLinha + " " + label + " " + comando;
			}
			novosCodigos.add(codigo);
		}
		this.codigos = novosCodigos;
		
	}

	/**
	 * (Re)Inicializa a tabela de labels
	 *
	 */
	public void resetTabLabel() {
		this.tabelaDeLabels.clear();
		
	}

}
